﻿using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.WowAddonStudio.Lua.LanguageService.Intellisense.Providers;
using Microsoft.WowAddonStudio.Lua.LanguageService.Model;
using Microsoft.WowAddonStudio.Lua.LanguageService.Support;
using Microsoft.WowAddonStudio.Lua.Parser;
using Microsoft.WowAddonStudio.Services;

using LuaParser = Microsoft.WowAddonStudio.Lua.Parser.Parser;

namespace Microsoft.WowAddonStudio.Lua.LanguageService
{
    /// <summary>
    /// The language service provided for the Lua language.
    /// </summary>
    [Guid(GuidStrings.LuaLanguageService)]
    public class LuaLanguageService : VisualStudio.Package.LanguageService, ILuaLanguageService
    {
        private const string documentationRelativePath = @"Lua\Documentation";
        private LanguagePreferences preferences;
        private IScanner scanner;

        private XmlDocumentationDeclarationProvider documentationDeclarationProvider;

        /// <summary>
        /// Gets the name of the language.
        /// </summary>
        public override string Name
        {
            get { return Resources.Lua; }
        }

        /// <summary>
        /// Initializes the Lua language service.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            // Initialize the documentation
            this.InitializeDocumentation();
        }

        private void InitializeDocumentation()
        {
            // Get the install directory of AddOn Studio an initialize the DocumentationDeclarationProvider
            var installDirectory = this.GetInstallDirectory();
            if (installDirectory != null)
            {
                var documentationPath = Path.Combine(installDirectory, documentationRelativePath);

                if (Directory.Exists(documentationPath))
                    documentationDeclarationProvider = new XmlDocumentationDeclarationProvider(documentationPath);
            }
        }

        /// <summary>
        /// Gets the preferences of the language.
        /// </summary>
        /// <returns>An instance of the <see cref="LanguagePreferences"/> class.</returns>
        public override LanguagePreferences GetLanguagePreferences()
        {
            if (preferences == null)
            {
                preferences = new LanguagePreferences(Site, typeof(LuaLanguageService).GUID, Name);
                preferences.Init();
             }

            return preferences;            
        }
        
        /// <summary>
        /// Returns the scanner used for the language service.
        /// </summary>
        /// <param name="buffer">An <see cref="IVsTextLines"/> representing the lines of source to parse. </param>
        /// <returns>Returns an <see cref="IScanner"/> object.</returns>
        public override IScanner GetScanner(IVsTextLines buffer)
        {
            if (scanner == null)
                scanner = new LineScanner();

            return scanner;            
        }

        public override ParseRequest CreateParseRequest(Source source, int line, int column, TokenInfo tokenInfo, string sourceText, string filename, ParseReason reason, IVsTextView view)
        {
            return new LuaParseRequest(line, column, tokenInfo, sourceText, filename, reason, view, source.CreateAuthoringSink(reason, line, column), !this.Preferences.EnableAsyncCompletion);
        }

        /// <summary>
        /// Creates the <see cref="Source"/> object for a buffer.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <returns>An instance of the <see cref="Source"/> class.</returns>
        public override Source CreateSource(IVsTextLines buffer)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");

            return new LuaSource(this, buffer, GetColorizer(buffer));
        }

        /// <summary>
        /// Creates the view filter.
        /// </summary>
        /// <param name="codeWindowManager">The code window manager.</param>
        /// <param name="textView">The new view.</param>
        /// <returns></returns>
        public override ViewFilter CreateViewFilter(CodeWindowManager codeWindowManager, IVsTextView textView)
        {
            return new LuaViewFilter(codeWindowManager, textView);
        }

        /// <summary>
        /// Parses the source based on the specified <see cref="ParseRequest"/> object. 
        /// </summary>
        /// <param name="parseRequest">The <see cref="ParseRequest"/> describing how to parse the source file.</param>
        /// <returns>If successful, returns an <see cref="AuthoringScope"/> object; otherwise, returns a null value. </returns>
        public override AuthoringScope ParseSource(ParseRequest parseRequest)
        {
            if (parseRequest == null)
                throw new ArgumentNullException("parseRequest");

            Trace.WriteLine(String.Format("ParseSource: {0} ({1}:{2}), Reason = {3}", parseRequest.FileName, parseRequest.Line, parseRequest.Col, parseRequest.Reason));

            // Cast the request to the appropriate type
            var request = (LuaParseRequest)parseRequest;
            
            // Create a parser for the request
            var parser = LuaParser.CreateParser<DocumentationParser>(request.Text);

#if TRACEPARSER
            // Enable tracing on the parser
            parser.Tracing = true;
#endif

            // Initialize authoring scope
            var authoringScope = new LuaAuthoringScope(this, request);

            // Check whether the documentation declaration provider is available
            if (documentationDeclarationProvider != null)
            {
                // Add documentation declaration provider
                authoringScope.AddProvider(documentationDeclarationProvider);
            }

            // Add declaration providers
            authoringScope.AddProvider(new TraceDeclarationProvider());
            authoringScope.AddProvider(new SnippetDeclarationProvider(this));
            authoringScope.AddProvider(new KeywordDeclarationProvider());
            authoringScope.AddProvider(new ParserDeclarationProvider(parser));

            // Retrieve library manager
            var libraryManager = (LibraryManager)this.GetService(typeof(ILibraryManager));
            if (libraryManager != null)
            {
                // Iterate through hierarchy managers and add hierarchy declaration providers
                foreach (var hierarchyManager in libraryManager.HierarchyManagers)
                    authoringScope.AddProvider(new HierarchyDeclarationProvider(hierarchyManager.CreateStreamProvider(), documentationDeclarationProvider));
            }

            // Set the authoring sink of the request for the parser
            parser.Sink = request.Sink;

            // Perform the parse for the request
            if (parser.Parse())
            {
                // Store the parsed chunk as part of the completed request
                request.Chunk = parser.Chunk;
            }

            // Return the authoring scope
            return authoringScope;
        }

        public override void OnIdle(bool periodic)
        {
            base.OnIdle(periodic);

            Source source = GetSource(this.LastActiveTextView);
            if (source != null && source.LastParseTime == Int32.MaxValue)
                source.LastParseTime = 0;
        }

        /// <summary>
        /// Returns a list of file extension filters suitable for a Save As dialog box. 
        /// </summary>
        /// <returns>If successful, returns a string containing the file extension filters; otherwise, returns an empty string.</returns>
        public override string GetFormatFilterList()
        {
            return Resources.LuaFormatFilter;
        }

        private string GetInstallDirectory()
        {
            // Get the IVsShell service
            var shell = (IVsShell)GetService(typeof(IVsShell));

            if (shell != null)
            {
                // Retrieve the install directory from the shell
                object installDirectoryValue;
                shell.GetProperty((int)__VSSPROPID.VSSPROPID_InstallDirectory, out installDirectoryValue);

                if (installDirectoryValue != null)
                    return (string)installDirectoryValue;
            }

            return null;
        }
    }
}